Hi!大家好啊!因為大家的支持!鐵人賽總算過一半了!希望剩下來的賽程請各位大大繼續指教!
話說今天為了研究一個東西浪費了一堆時間,讓我現在感覺很沒勁,哈哈哈(超級苦笑XD)!我們準備要進入最後的React Router篇了(雖然今天可能還不會提到主角本身XD),為了迎接他,這一篇先來了解React的目錄結構!
先前我們做的那些練習,都把jsx、js、html包含設定檔全部都丟在一起,相信如果有強迫症的大大,從第一篇忍到現在一定看得很辛苦!其實我在一開始就想要提目錄結構了,但是因為還有Redux的關係,就想說一次講一講,在這邊和各位大大說聲不好意思!
說到結構這種東西,真的沒有基本的說法,有時候隨著公司的案子結構,有時候又照著維護案的結構,但是還是有主流的一個基本結構,至於要怎麼調整,大家可以為自己的目錄結構做個客製化XD,那以下先說說最簡單的基本目錄:
以下解釋一下每個資料夾的內容及用途:
package.json和webpack.config.js等設定檔。index.html和負責頁面渲染的index.jsx,雖然他最後會被webpack打包成index_bundle.js。index.js將export的內容匯出,這樣在其他文件中import的時候看起來比較不會亂,而Main組件負責最後的輸出,而這裡的Main組件也只是一個頁面,當然也可以在實務上建立各個頁面匯出的總組件,所以也可以改成這樣子放:
index.js統一匯出,因為當我們使用import時,會預設取目錄文件的index.js,詳細說明可以看這裡。有了目錄看起來專案的樣子是不是完整多了XD,可是為了保持整個目錄的結構,我們的程式碼也需要做相對應的修改:
webpack.config.js:src中,所以打包的目錄和輸出的目錄都要更改。
const path = require('path');
module.exports = {
    //這個webpack打包的對象,這裡面加上剛剛建立的index.js
    entry: {
        index: ['./src/index.jsx']
    },
    output: {
        //這裡是打包後的檔案名稱
        filename: 'src/index_bundle.js',
        //打包後的路徑,這裡使用path模組的resolve()取得絕對位置,也就是目前專案的根目錄
        path: path.resolve('./'),
    },
    module:{
        rules:[
            {test:/\.jsx$/,use:{loader:'babel-loader',options:{presets:['@babel/preset-react','@babel/preset-env']}}},
            {test:/\.js$/,use:{loader:'babel-loader',options:{presets:['@babel/preset-env']}}}
        ]
    }
};
Title和Main組件:
Title/Title.jsx:
import React from "react"
class Title extends React.Component {
    render(){
        return <h1>{this.props.title}</h1>
    }
}
//把該目錄的Title組件匯出
export {Title}
Title/index.js:import和export寫在一起的寫法,等於直接匯出剛剛在Title.jsx中export的所有物件。
export * from "./Title.jsx"
Main/Main.jsx:import進Title匯出的物件,因為前面有提到預設是index.js,所以只需要指定到目錄就好,如此一來就能夠使用Title組件了,最後一樣再把他匯出:
import React from "react"
import {Title} from "../Title"
class Main extends React.Component{
    render(){
        return <Title title="Hello!World!" />
    }
}
export {Main}
Main/index.js:export * from "./Main.jsx"
index.jsx:ReactDOM.render輸出到root中:
import React from "react"
import ReactDOM from "react-dom"
import {Main} from "./components/Main"
ReactDOM.render(<Main />,document.getElementById('root'));
完成後就可以試著打包或使用webpack-dev-server執行:
是不是覺得反而花更大的心力在處理這些事情了XD,不過在專案越來越大的時候,這樣子的分類反而不會搞混各個組件的位置!以下是基本目錄結構的GitHub連結:
GitHub目錄連結
基本的沒問題後,接著要來加入Redux了!所以應該算稍微進階吧XD,不過我一直一直在猶豫要不要用之前的留言版,因為調整目錄就幾乎整個都要變動了,會改到超級多東西(這邊有點浮誇啦XD),雖然猶豫到最後我還是Do了!哈哈哈!那下面就一起來改寫吧!目標是這樣子:
應該比第一次看的時候還要清楚多了!以下解釋每個資料夾和檔案的用途:
index.js統一匯出。action-types.js和要給store管理的資料。Reducer,一樣由index.js匯出。store,一樣由index.js匯出。接下來就重頭戲了,開始改寫之前留言板的內容吧!
action-types.js:export const ADD_MESSAGE = "ADD_MESSAGE"
models.js:const data = {message:[{id:'1',name:'神Q',message:'嗨!大家好啊!'},
{id:'2',name:'小馬',message:'早安啊!昨天有沒有好好發文?'},
{id:'3',name:'王子',message:'ㄛ!別說了,那真的超級累!'},
{id:'4',name:'神Q',message:'哈哈哈!加油啦!再一下就結束了!'},
{id:'5',name:'王子',message:'結束後我一定要爆睡一頓!'},]}
export {data}
message.js:action-types.js中匯入指令後建構動作並匯出:
import {ADD_MESSAGE} from "../constants/action-types.js"
export const addMessage = message => ({
    type : ADD_MESSAGE, payload : message
})
index.js:export * from "./message.js"
messageReducer.js:models.js的資料和message.js建構的動作做成一個reducer,一樣在最後把他匯出:
import {ADD_MESSAGE} from "../constants/action-types.js"
import {data} from "../constants/models.js"
const messageReducer = (state = data,action) =>{
    switch(action.type){
        case ADD_MESSAGE:{
            action.payload.id = String(state.message.length+1)
            return { ...state, message: [...state.message, action.payload] }
            break
        }
        default:{
            return state
            break
        }
    }
}
export {messageReducer}
index.js:reducer並匯出:
export * from "./messageReducer.js"
store:
configureStore.js匯入reducer來產生store:
import {createStore} from "redux"
import {messageReducer} from "../reducers"
const store = createStore(messageReducer)
export {store}
index.js負責整理匯出:
export * from "./configureStore.js"
connect後才交給Main去組合出最後的頁面匯出:
InputMessage.jsx:
addMessage這個動作。mapDispatchToProps指定對store執行dispatch動作。connect,產生InputMessage並匯出import React from "react"
import {connect} from "react-redux"
import {addMessage} from "../../actions"
class ConnectInputMessage extends React.Component {
    constructor(props){
        super(props)
        this.state = ({name:'',message:''})
        this.changeState = this.changeState.bind(this)
        this.clearMessage = this.clearMessage.bind(this)
        this.submitMessage = this.submitMessage.bind(this)
    }
    changeState(event){
        this.setState({[event.target.name]:event.target.value})
    }
    clearMessage(){
        this.setState({name:'',message:''})
    }
    submitMessage(){
        let messageData = {
            name:this.state.name,
            message:this.state.message,
        }
        this.props.addMessage(messageData)
        this.clearMessage()
    }
    render(){
        return(
            <div>
                暱稱:<input type="text" name="name" 
                            value={this.state.name}
                            onChange={this.changeState} />
                <br/>
                訊息:
                <br/><textarea name="message" 
                                value={this.state.message}
                                onChange={this.changeState}></textarea>
                <input type="button" value="送出留言"
                        onClick={this.submitMessage} />
            </div>
        )
    }
}
const mapDispatchToProps = dispatch => {
    return {
        addMessage : message =>{ dispatch(addMessage(message)) } 
    }
}
const InputMessage = connect(null,mapDispatchToProps)(ConnectInputMessage)
export {InputMessage}
MessageList.jsx:
mapStateToProps指定要向state取的資料。ConnectMessageList和mapStateToProps做connect產生MessageList後匯出。import React from "react"
import {connect} from "react-redux"
class ConnectMessageList extends React.Component {
    render(){
        let message = this.props.message.map((item)=>{
            return <li key={item.id}>{item.name}:{item.message}</li>
        })
        return(
            <ul>
                {message}
            </ul>
        )
    }
}
const mapStateToProps = state =>{
    return {message : state.message}
}
const MessageList = connect(mapStateToProps)(ConnectMessageList)
export {MessageList}
Main.jsx:MessageList和InputMessage組件,並將它們組合起來後匯出:
import React from "react"
import {MessageList} from "../MessageList"
import {InputMessage} from "../InputMessage"
class Main extends React.Component{
    render(){
        return (
            <div>
                <InputMessage />
                <MessageList />
            </div>
        )
    }
}
export {Main}
idnex.js的內容,但是一樣都是他們提供匯出的功能,就和第一段一樣,我就不再另外PO了!只是不能忘記哦!index.js的HTML和上一個例子一樣,差別在要打包的index.jsx內容:store和連結react及redux用的Provider組件,之後把Main包在Provider中用render渲染到畫面上。
import React from "react"
import ReactDOM from "react-dom"
import {Provider} from "react-redux"
import {store} from "./store"
import {Main} from "./components/Main"
ReactDOM.render(<Provider store={store}>
                    <Main />
                </Provider>
                ,document.getElementById('root'));
雖然看起來很複雜,但是經過調整基本架構後再加入redux進目錄中,感覺就比較不會那麼卡手了,只要對各個資料夾及檔案負責的內容清楚,剩下就只是import和export的事情而已,這裡附上留言板的目錄架構:
GitHub目錄架構連結
因為是留言板,所以也有Page:GitPage連結
小弟我第一次就拿留言板來改,結果怎麼改都錯誤,才決定砍掉從練從Hello!World!開始,搞到最後這篇感覺像半複習一樣XD,還請各位多多包涵,另外因為一開始就有提到說,目錄架構真的有非常非常多種,幾乎沒有任何一種標準答案,所以如果大大們有推薦的架構或可以改進的,麻煩留言告訴我!小弟我感激不盡
最後要感謝各位大大的觀看,如果文中有任何錯誤或解釋不清楚的地方,還麻煩各位大大留言告訴我,小弟會盡快修改會補充文章內容的,謝謝大家
參考資料:
這篇的內容,好豐富啊......等之後有空時,再來照大大練習。 謝謝!
我以為大大是創作派的!
原來是我有眼不識泰山
之前維護的 Angular 專案也有類似 index.js 的用法,每個資料夾下會有一個檔案用來整理匯出,不過我不喜歡這種用法,哈哈哈。
其實我是第一次接觸到這種用法,
因為目前用React做的都是小作品,
所以只有覺得在import時寫起來很簡潔很方便,
不曉得大大有甚麼看法